//import org.apache.calcite.config.CalciteConnectionConfigImpl;
import org.apache.calcite.config.CalciteConnectionConfigImpl;
import org.apache.calcite.jdbc.CalciteConnection;
import org.apache.calcite.jdbc.CalciteSchema;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.hep.HepPlanner;
import org.apache.calcite.plan.hep.HepProgramBuilder;
import org.apache.calcite.prepare.CalciteCatalogReader;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelRoot;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.rel.type.RelDataTypeSystemImpl;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.impl.AbstractTable;
import org.apache.calcite.sql.SqlExplainFormat;
import org.apache.calcite.sql.SqlExplainLevel;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.type.BasicSqlType;
import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorUtil;
import org.apache.calcite.sql2rel.RelDecorrelator;
import org.apache.calcite.sql2rel.SqlToRelConverter;
import org.apache.calcite.sql2rel.StandardConvertletTable;
import org.apache.calcite.tools.FrameworkConfig;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.tools.RelBuilder;
import org.codehaus.commons.compiler.CompilerFactoryFactory;
import org.codehaus.commons.compiler.ICompilerFactory;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

public class TestSchema {
    /**
     * select u.id as user_id, u.name as user_name, j.company as user_company, u.age as user_age
     * from users u join jobs j on u.name=j.name
     * where u.age > 30 and j.id>10
     * order by user_id
     *
     * @param args
     */
    public static void main(String[] args) throws SqlParseException, ClassNotFoundException, SQLException {
        System.out.println(CompilerFactoryFactory.class.getProtectionDomain().getCodeSource().getLocation());
        System.out.println(ICompilerFactory.class.getProtectionDomain().getCodeSource().getLocation());
        /**
         * Class.forName("com.mysql.jdbc.Driver");
         * BasicDataSource dataSource = new BasicDataSource(); dataSource.setUrl("jdbc:mysql://localhost");
         * dataSource.setUsername("username");
         * dataSource.setPassword("password");
         * Schema schema = JdbcSchema.create(rootSchema, "hr", dataSource, null, "name");
         */

        SchemaPlus rootSchema = Frameworks.createRootSchema(true);

        //Class.forName("org.apache.calcite.jdbc.Driver");
        Properties info = new Properties();
       // info.setProperty("lex", "JAVA");
        info.setProperty("caseSensitive", "false");
        Connection connection =
                DriverManager.getConnection("jdbc:calcite:", info);
        CalciteConnection calciteConnection =
                connection.unwrap(CalciteConnection.class);
       // SchemaPlus rootSchema = calciteConnection.getRootSchema();

        rootSchema.add("USERS", new AbstractTable() { //note: add a table
            @Override
            public RelDataType getRowType(final RelDataTypeFactory typeFactory) {
                RelDataTypeFactory.Builder builder = typeFactory.builder();
                builder.add("ID", new BasicSqlType(new RelDataTypeSystemImpl() {
                }, SqlTypeName.INTEGER));
                builder.add("NAME", new BasicSqlType(new RelDataTypeSystemImpl() {
                }, SqlTypeName.CHAR));
                builder.add("AGE", new BasicSqlType(new RelDataTypeSystemImpl() {
                }, SqlTypeName.INTEGER));
                return builder.build();
            }
        });

        rootSchema.add("JOBS", new AbstractTable() {
            @Override
            public RelDataType getRowType(final RelDataTypeFactory typeFactory) {
                RelDataTypeFactory.Builder builder = typeFactory.builder();

                builder.add("ID", new BasicSqlType(new RelDataTypeSystemImpl() {
                }, SqlTypeName.INTEGER));
                builder.add("NAME", new BasicSqlType(new RelDataTypeSystemImpl() {
                }, SqlTypeName.CHAR));
                builder.add("COMPANY", new BasicSqlType(new RelDataTypeSystemImpl() {
                }, SqlTypeName.CHAR));
                return builder.build();
            }
        });
        String sql = "select u.id as user_id, u.name as user_name, j.company as user_company, u.age as user_age " +
                " from users u join jobs j on u.name=j.name " +
                " where u.age > 30 and j.id>10 " +
                " order by user_id";
        //step1、SQL 解析阶段（SQL–>SqlNode）
        SqlParser parser = SqlParser.create(sql, SqlParser.Config.DEFAULT);
        SqlNode sqlNode = parser.parseStmt();
        //Calcite 使用 JavaCC 做 SQL 解析，JavaCC 根据 Calcite 中定义的 Parser.jj 文件，生成一系列的 java 代码，
        // 生成的 Java 代码会把 SQL 转换成 AST 的数据结构（这里是 SqlNode 类型）。
        //与 Javacc 相似的工具还有 ANTLR，JavaCC 中的 jj 文件也跟 ANTLR 中的 G4文件类似，Apache Spark 中使用这个工具做类似的事情。

        //step2、SqlNode 验证（SqlNode–>SqlNode）
        //经过上面的第一步，会生成一个 SqlNode 对象，它是一个未经验证的抽象语法树，下面就进入了一个语法检查阶段，语法检查前需要知道元数据信息，
        // 这个检查会包括表名、字段名、函数名、数据类型的检查。进行语法检查的实现如下：
        //note: 二、sql validate（会先通过Catalog读取获取相应的metadata和namespace）
//note: get metadata and namespace
        SqlTypeFactoryImpl factory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
        CalciteCatalogReader calciteCatalogReader = new CalciteCatalogReader(
                CalciteSchema.from(rootSchema),
                CalciteSchema.from(rootSchema).path(null),
                factory,
                new CalciteConnectionConfigImpl(new Properties()));

//note: 校验（包括对表名，字段名，函数名，字段类型的校验。）
        SqlParser.Config insensitiveParser = SqlParser.config().withCaseSensitive(false);

        FrameworkConfig frameworkConfig = Frameworks.newConfigBuilder()
                .parserConfig(insensitiveParser)
                .defaultSchema(rootSchema)
                .build();
        SqlValidator validator = SqlValidatorUtil.newValidator(SqlStdOperatorTable.instance(),
                calciteCatalogReader, factory,
                SqlValidator.Config.DEFAULT);
        SqlNode validateSqlNode = validator.validate(sqlNode);





        //Step3、语义分析（SqlNode–>RelNode/RexNode）
        //经过第二步之后，这里的 SqlNode 就是经过语法校验的 SqlNode 树，接下来这一步就是将 SqlNode 转换成 RelNode/RexNode，
        // 也就是生成相应的逻辑计划（Logical Plan），示例的代码实现如下：
// create the rexBuilder
        final RexBuilder rexBuilder = new RexBuilder(factory);
// init the planner
// 这里也可以注册 VolcanoPlanner，这一步 planner 并没有使用
        HepProgramBuilder builder = new HepProgramBuilder();
        RelOptPlanner planner = new HepPlanner(builder.build());

//note: init cluster: An environment for related relational expressions during the optimization of a query.
        final RelOptCluster cluster = RelOptCluster.create(planner, rexBuilder);
//note: init SqlToRelConverter
        final SqlToRelConverter.Config config = SqlToRelConverter.config();
              /*  .withConfig(frameworkConfig.getSqlToRelConverterConfig())
                .withTrimUnusedFields(false)
                .withConvertTableAccess(false)
                .build(); //note: config*/
// 创建 SqlToRelConverter 实例，cluster、calciteCatalogReader、validator 都传进去了，SqlToRelConverter 会缓存这些对象
        final SqlToRelConverter sqlToRelConverter = new SqlToRelConverter(new DogView(), validator, calciteCatalogReader, cluster, StandardConvertletTable.INSTANCE, config);
// convert to RelNode
        RelRoot root = sqlToRelConverter.convertQuery(validateSqlNode, false, true);

        System.out.println();
        System.out.println(RelOptUtil.dumpPlan("[Logical plan]", root.rel, SqlExplainFormat.TEXT, SqlExplainLevel.NON_COST_ATTRIBUTES));


        root = root.withRel(sqlToRelConverter.flattenTypes(root.rel, true));
        final RelBuilder relBuilder = config.getRelBuilderFactory().create(cluster, null);
        root = root.withRel(RelDecorrelator.decorrelateQuery(root.rel, relBuilder));
        RelNode relNode = root.rel;

        planner.setRoot(root.rel);
        RelNode phyPlan = planner.findBestExp();
        System.out.println(RelOptUtil.dumpPlan("[Physical plan]", phyPlan, SqlExplainFormat.TEXT, SqlExplainLevel.NON_COST_ATTRIBUTES));


    }

    //DogView 的实现
    private static class DogView implements RelOptTable.ViewExpander {
        public DogView() {
        }

        @Override
        public RelRoot expandView(RelDataType rowType, String queryString, List<String> schemaPath,
                                  List<String> viewPath) {
            return null;
        }
    }
}
